// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/chunked_upload_data_stream.h"

#include <memory>
#include <string>

#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/base/upload_data_stream.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace {

    const char kTestData[] = "0123456789";
    const size_t kTestDataSize = arraysize(kTestData) - 1;
    const size_t kTestBufferSize = 1 << 14; // 16KB.

} // namespace

// Reads data once from the upload data stream, and returns the data as string.
// Expects the read to succeed synchronously.
std::string ReadSync(UploadDataStream* stream, int buffer_size)
{
    scoped_refptr<IOBuffer> buf = new IOBuffer(buffer_size);
    int result = stream->Read(buf.get(),
        buffer_size,
        TestCompletionCallback().callback());
    EXPECT_GE(result, 0);
    return std::string(buf->data(), result);
}

// Check the case data is added after the first read attempt.
TEST(ChunkedUploadDataStreamTest, AppendOnce)
{
    ChunkedUploadDataStream stream(0);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    TestCompletionCallback callback;
    scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
    int result = stream.Read(buf.get(), kTestBufferSize, callback.callback());
    ASSERT_EQ(ERR_IO_PENDING, result);

    stream.AppendData(kTestData, kTestDataSize, true);
    int read = callback.WaitForResult();
    ASSERT_GE(read, 0);
    EXPECT_EQ(kTestData, std::string(buf->data(), read));
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(kTestDataSize, stream.position());
    EXPECT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, AppendOnceBeforeRead)
{
    ChunkedUploadDataStream stream(0);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    stream.AppendData(kTestData, kTestDataSize, true);
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    std::string data = ReadSync(&stream, kTestBufferSize);
    EXPECT_EQ(kTestData, data);
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(kTestDataSize, stream.position());
    EXPECT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, AppendOnceBeforeInit)
{
    ChunkedUploadDataStream stream(0);

    stream.AppendData(kTestData, kTestDataSize, true);
    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    std::string data = ReadSync(&stream, kTestBufferSize);
    EXPECT_EQ(kTestData, data);
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(kTestDataSize, stream.position());
    EXPECT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, MultipleAppends)
{
    ChunkedUploadDataStream stream(0);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size());
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    TestCompletionCallback callback;
    scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
    for (size_t i = 0; i < kTestDataSize; ++i) {
        EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
        EXPECT_EQ(i, stream.position());
        ASSERT_FALSE(stream.IsEOF());
        int bytes_read = stream.Read(buf.get(),
            kTestBufferSize,
            callback.callback());
        ASSERT_EQ(ERR_IO_PENDING, bytes_read);
        stream.AppendData(&kTestData[i], 1, i == kTestDataSize - 1);
        ASSERT_EQ(1, callback.WaitForResult());
        EXPECT_EQ(kTestData[i], buf->data()[0]);
    }

    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(kTestDataSize, stream.position());
    ASSERT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, MultipleAppendsBetweenReads)
{
    ChunkedUploadDataStream stream(0);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
    for (size_t i = 0; i < kTestDataSize; ++i) {
        EXPECT_EQ(i, stream.position());
        ASSERT_FALSE(stream.IsEOF());
        stream.AppendData(&kTestData[i], 1, i == kTestDataSize - 1);
        int bytes_read = stream.Read(buf.get(),
            kTestBufferSize,
            TestCompletionCallback().callback());
        ASSERT_EQ(1, bytes_read);
        EXPECT_EQ(kTestData[i], buf->data()[0]);
    }

    EXPECT_EQ(kTestDataSize, stream.position());
    ASSERT_TRUE(stream.IsEOF());
}

// Checks that multiple reads can be merged.
TEST(ChunkedUploadDataStreamTest, MultipleAppendsBeforeInit)
{
    ChunkedUploadDataStream stream(0);
    stream.AppendData(kTestData, 1, false);
    stream.AppendData(kTestData + 1, 1, false);
    stream.AppendData(kTestData + 2, kTestDataSize - 2, true);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    std::string data = ReadSync(&stream, kTestBufferSize);
    EXPECT_EQ(kTestData, data);
    EXPECT_EQ(kTestDataSize, stream.position());
    ASSERT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, MultipleReads)
{
    // Use a read size different from the write size to test bounds checking.
    const size_t kReadSize = kTestDataSize + 3;

    ChunkedUploadDataStream stream(0);
    stream.AppendData(kTestData, kTestDataSize, false);
    stream.AppendData(kTestData, kTestDataSize, false);
    stream.AppendData(kTestData, kTestDataSize, false);
    stream.AppendData(kTestData, kTestDataSize, true);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    std::string data = ReadSync(&stream, kReadSize);
    EXPECT_EQ("0123456789012", data);
    EXPECT_EQ(kReadSize, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    data = ReadSync(&stream, kReadSize);
    EXPECT_EQ("3456789012345", data);
    EXPECT_EQ(2 * kReadSize, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    data = ReadSync(&stream, kReadSize);
    EXPECT_EQ("6789012345678", data);
    EXPECT_EQ(3 * kReadSize, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    data = ReadSync(&stream, kReadSize);
    EXPECT_EQ("9", data);
    EXPECT_EQ(4 * kTestDataSize, stream.position());
    EXPECT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, EmptyUpload)
{
    ChunkedUploadDataStream stream(0);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    TestCompletionCallback callback;
    scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
    int result = stream.Read(buf.get(), kTestBufferSize, callback.callback());
    ASSERT_EQ(ERR_IO_PENDING, result);

    stream.AppendData(NULL, 0, true);
    int read = callback.WaitForResult();
    EXPECT_EQ(0, read);
    EXPECT_EQ(0u, stream.position());
    EXPECT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, EmptyUploadEndedBeforeInit)
{
    ChunkedUploadDataStream stream(0);
    stream.AppendData(NULL, 0, true);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    std::string data = ReadSync(&stream, kTestBufferSize);
    ASSERT_EQ("", data);
    EXPECT_EQ(0u, stream.position());
    EXPECT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, RewindAfterComplete)
{
    ChunkedUploadDataStream stream(0);
    stream.AppendData(kTestData, 1, false);
    stream.AppendData(kTestData + 1, kTestDataSize - 1, true);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    std::string data = ReadSync(&stream, kTestBufferSize);
    EXPECT_EQ(kTestData, data);
    EXPECT_EQ(kTestDataSize, stream.position());
    ASSERT_TRUE(stream.IsEOF());

    // Rewind stream and repeat.
    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    data = ReadSync(&stream, kTestBufferSize);
    EXPECT_EQ(kTestData, data);
    EXPECT_EQ(kTestDataSize, stream.position());
    ASSERT_TRUE(stream.IsEOF());
}

TEST(ChunkedUploadDataStreamTest, RewindWhileReading)
{
    ChunkedUploadDataStream stream(0);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    TestCompletionCallback callback;
    scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
    int result = stream.Read(buf.get(), kTestBufferSize, callback.callback());
    ASSERT_EQ(ERR_IO_PENDING, result);

    ASSERT_EQ(OK, stream.Init(TestCompletionCallback().callback()));
    EXPECT_FALSE(stream.IsInMemory());
    EXPECT_EQ(0u, stream.size()); // Content-Length is 0 for chunked data.
    EXPECT_EQ(0u, stream.position());
    EXPECT_FALSE(stream.IsEOF());

    // Adding data now should not result in calling the original read callback,
    // since the stream was re-initialized for reuse, which cancels all pending
    // reads.
    stream.AppendData(kTestData, kTestDataSize, true);
    EXPECT_FALSE(callback.have_result());

    std::string data = ReadSync(&stream, kTestBufferSize);
    EXPECT_EQ(kTestData, data);
    EXPECT_EQ(kTestDataSize, stream.position());
    ASSERT_TRUE(stream.IsEOF());
    EXPECT_FALSE(callback.have_result());
}

// Check the behavior of ChunkedUploadDataStream::Writer.
TEST(ChunkedUploadDataStreamTest, ChunkedUploadDataStreamWriter)
{
    std::unique_ptr<ChunkedUploadDataStream> stream(
        new ChunkedUploadDataStream(0));
    std::unique_ptr<ChunkedUploadDataStream::Writer> writer(
        stream->CreateWriter());

    // Write before Init.
    ASSERT_TRUE(writer->AppendData(kTestData, 1, false));
    ASSERT_EQ(OK, stream->Init(TestCompletionCallback().callback()));

    // Write after Init.
    ASSERT_TRUE(writer->AppendData(kTestData + 1, kTestDataSize - 1, false));

    TestCompletionCallback callback;
    std::string data = ReadSync(stream.get(), kTestBufferSize);
    EXPECT_EQ(kTestData, data);

    // Writing data should gracefully fail if the stream is deleted while still
    // appending data to it.
    stream.reset();
    EXPECT_FALSE(writer->AppendData(kTestData, kTestDataSize, true));
}

} // namespace net
